%% logem.erl:
%% Ember logging interface.

-module(logem).

-include_lib("eunit/include/eunit.hrl").

-include("ember_logger.hrl").

-export([log/2,log/3,log/4]).

-export([trace/1,trace/2,trace/3]).
-export([debug/1,debug/2,debug/3]).
-export([info/1,info/2,info/3]).
-export([warn/1,warn/2,warn/3]).
-export([error/1,error/2,error/3]).


%%=============================================================================
%% External API
%%=============================================================================

%% log(...): Generic logging, with the log level as first argument.
%%

log(Level, String) ->
    log(Level, "~w", [String], no_sync).

log(Level, Frm, Args) ->
    log(Level, Frm, Args, no_sync).

log(Level, Frm, Args, SyncLevel) 
  when is_list(Frm) andalso is_list(Args) andalso 
       ((level =:= trace) orelse
        (level =:= debug) orelse
        (level =:= info) orelse
        (level =:= warn) orelse
        (level =:= error)) andalso
       ((SyncLevel =:= no_sync) orelse (SyncLevel =:= sync) orelse
        is_integer(SyncLevel) orelse is_float(SyncLevel)) ->
    sendLog(Level, Frm, Args, SyncLevel).


%% Per-level functions:
%%

trace(S) -> log(trace, S).
trace(F,A) -> log(trace, F, A).
trace(F,A,S) -> log(trace, F, A, S).

debug(S) -> log(debug, S).
debug(F,A) -> log(debug, F, A).
debug(F,A,S) -> log(debug, F, A, S).

info(S) -> log(info, S).
info(F,A) -> log(info, F, A).
info(F,A,S) -> log(info, F, A, S).

warn(S) -> log(warn, S).
warn(F,A) -> log(warn, F, A).
warn(F,A,S) -> log(warn, F, A, S).

error(S) -> log(error, S).
error(F,A) -> log(error, F, A).
error(F,A,S) -> log(error, F, A, S).


%%=============================================================================
%% Utils
%%=============================================================================

sendToProxy(Message) ->
    ?EMBER_LOGGER_PROXY ! Message.
    

sync() ->
    sendToProxy({sync, self()}),
    receive ember_logger_sync -> ok end.


sendLog(Level, Frm, Args) ->
    sendToProxy({log, Level, Frm, Args}).


sendLog(Level, Frm, Args, no_sync) ->
    sendLog(Level, Frm, Args);

sendLog(Level, Frm, Args, sync) ->
    sendLog(Level, Frm, Args, 100);

sendLog(Level, Frm, Args, 0) ->
    sendLog(Level, Frm, Args);

sendLog(Level, Frm, Args, SyncLevel)
  when is_integer(SyncLevel) orelse is_float(SyncLevel) ->
    C = random:uniform(100),
    case SyncLevel < C of
        true ->
            sync();
        false ->
            ok
    end,
    sendLog(Level, Frm, Args).


%%=============================================================================
%% Unit Tests: Scaffolding
%%=============================================================================

start_link_false_proxy() ->
    Parent = self(),
    Pid = spawn_link(fun() -> false_proxy_loop(?EMBER_LOGGER_PROXY,
                                               Parent,
                                               none,
                                               none,
                                               0,
                                               0)
                     end),
    erlang:register(?EMBER_LOGGER_PROXY, Pid),
    Pid.


%% This will synchronously stop the False Proxy.
%% Remember to unlink it first!!!
stop_false_proxy(Pid) ->
    Pid ! {stop, self()},
    receive {stopped, Pid} -> ok end.
            

get_last_format(Pid) ->
    Pid ! {get_last_format, self()},
    receive {last_format, Pid, Format} -> Format end.


get_last_args(Pid) ->
    Pid ! {get_last_args, self()},
    receive {last_args, Pid, Args} -> Args end.


get_nsyncs(Pid) ->
    Pid ! {get_nsyncs, self()},
    receive {nsyncs, Pid, NSyncs} -> NSyncs end.


get_nunknown(Pid) ->
    Pid ! {get_nunknown, self()},
    receive {nunknown, Pid, NUnknown} -> NUnknown end.
    

false_proxy_loop(MyName, Parent, LastFormat, LastArgs, NSyncs, NUnk) ->
    receive
        {stop, From} ->
            From ! {stopped, self()};

        {get_last_format, From} ->
            From ! {last_format, self(), LastFormat},
            false_proxy_loop(MyName,Parent,LastFormat,LastArgs,NSyncs,NUnk);

        {get_last_args, From} ->
            From ! {last_args, self(), LastArgs},
            false_proxy_loop(MyName,Parent,LastFormat,LastArgs,NSyncs,NUnk);

        {get_nsyncs, From} ->
            From ! {nsyncs, self(), NSyncs},
            false_proxy_loop(MyName,Parent,LastFormat,LastArgs,NSyncs,NUnk);

        {get_nunknown, From} ->
            From ! {nunknown, self(), NUnk},
            false_proxy_loop(MyName,Parent,LastFormat,LastArgs,NSyncs,NUnk);

        {log, _Level, Format, Args} ->
            false_proxy_loop(MyName,Parent,Format,Args,NSyncs,NUnk);

        {sync, From} ->
            From ! ember_logger_sync,
            false_proxy_loop(MyName,Parent,LastFormat,LastArgs,NSyncs+1,NUnk);

        _Other ->
            false_proxy_loop(MyName,Parent,LastFormat,LastArgs,NSyncs,NUnk+1)
    
    end.
    


%%=============================================================================
%% Unit Tests: Tests.
%%=============================================================================

false_proxy_1_test() ->
    %%?debugMsg("start"),
    Pid = start_link_false_proxy(),
    Mon = erlang:monitor(process, Pid),
    %%?debugMsg("checks"),
    ?assertEqual(true, is_pid(Pid)),
    %%?debugMsg("get_format"),
    ?assertEqual(none, get_last_format(Pid)),
    %%?debugMsg("get_args"),
    ?assertEqual(none, get_last_args(Pid)),
    %%?debugMsg("get_nsyncs=0"),
    ?assertEqual(0, get_nsyncs(Pid)),
    %%?debugMsg("get_nunknown=0"),
    ?assertEqual(0, get_nunknown(Pid)),
    %%?debugMsg("check_is_member"),
    ?assertEqual(true, lists:member(Pid, processes())),
    %%?debugMsg("stop"),
    stop_false_proxy(Pid),
    receive {'DOWN', Mon, _, _, _} -> ok end,
    %%?debugMsg("check_not_member"),
    ?assertEqual(false, lists:member(Pid, processes())),
    %%?debugMsg("done"),
    ok.


syncs_1_test() ->
    %%?debugMsg("start"),
    Pid = start_link_false_proxy(),
    Mon = erlang:monitor(process, Pid),
    %%?debugMsg("checks"),
    ?assertEqual(0, get_nsyncs(Pid)),
    sync(),
    ?assertEqual(1, get_nsyncs(Pid)),
    sync(),
    ?assertEqual(2, get_nsyncs(Pid)),
    %%?debugMsg("stop"),
    stop_false_proxy(Pid),
    receive {'DOWN', Mon, _, _, _} -> ok end,
    %%?debugMsg("done"),
    ok.
    
unknowns_1_test() ->
    %%?debugMsg("start"),
    Pid = start_link_false_proxy(),
    Mon = erlang:monitor(process, Pid),
    %%?debugMsg("checks"),
    ?assertEqual(0, get_nsyncs(Pid)),
    ?assertEqual(0, get_nunknown(Pid)),
    ?EMBER_LOGGER_PROXY ! {bla, bla},
    ?assertEqual(1, get_nunknown(Pid)),
    ?EMBER_LOGGER_PROXY ! {bla, bla},
    sync(),
    ?assertEqual(2, get_nunknown(Pid)),
    ?assertEqual(1, get_nsyncs(Pid)),
    %%?debugMsg("stop"),
    stop_false_proxy(Pid),
    receive {'DOWN', Mon, _, _, _} -> ok end,
    %%?debugMsg("done"),
    ok.
    
